package mock

import (
	"context"
	"log"
	"strings"
	"sync/atomic"

	"github.com/panjf2000/gnet/v2"
)

type RedisServer struct {
	gnet.BuiltinEventEngine
	eng gnet.Engine

	Addr      string
	connected int32

	store *Store
}

func NewRedisServer(addr, path string) (*RedisServer, error) {
	s, err := NewStore(path)
	if err != nil {
		return nil, err
	}

	rs := &RedisServer{
		Addr:  addr,
		store: s,
	}
	return rs, nil
}

func (s *RedisServer) OnBoot(eng gnet.Engine) gnet.Action {
	log.Printf("redis server listening on %s\n", s.Addr)
	s.eng = eng
	return gnet.None
}

func (s *RedisServer) OnShutdown(eng gnet.Engine) {
	log.Printf("redis server shutdown")
}

func (s *RedisServer) OnOpen(c gnet.Conn) ([]byte, gnet.Action) {
	ctx := &ConnContext{
		decoder: NewDecoder(c),
	}
	c.SetContext(ctx)
	atomic.AddInt32(&s.connected, 1)
	return nil, gnet.None
}

func (s *RedisServer) OnClose(c gnet.Conn, err error) (action gnet.Action) {
	if err != nil {
		log.Printf("error occurred on connection=%s, %v\n", c.RemoteAddr().String(), err)
	}
	atomic.AddInt32(&s.connected, -1)
	return gnet.None
}

func (s *RedisServer) Shutdown() {
	if err := s.eng.Stop(context.TODO()); err != nil {
		log.Printf("stop server err:%s\n", err.Error())
	}
	s.store.Close()
}

func (s *RedisServer) OnTraffic(c gnet.Conn) gnet.Action {
	connCtx := c.Context().(*ConnContext)

	cmd, err := connCtx.decoder.ReadCommand()
	if err != nil {
		log.Printf("receive invalid package: %v\n", err)
		return gnet.Close
	}

	// TODO: 异步
	cmdCtx := &CmdContext{
		Name:    string(cmd[0]),
		Args:    cmd[1:],
		Conn:    c,
	}

	buf := Call(cmdCtx, s.store)

	_ = c.AsyncWrite(buf, func(c gnet.Conn, err error) error {
		if err != nil {
			log.Printf("conn:%s reply fail:%s\n", c.RemoteAddr().String(), err.Error())
			_ = c.Close()
		}
		return nil
	})

	return gnet.None
}

type ConnContext struct {
	decoder *Decoder
}


func Call(ctx *CmdContext, b *Store) []byte {
	ctx.Name = strings.ToLower(ctx.Name)

	cmdDesc, ok := Commands[ctx.Name]
	if !ok {
		return EncodeError(ErrUnKnownCommand(ctx.Name).Error())
	}

	if err := syntaxValid(ctx, cmdDesc.Cons); err != nil {
		return EncodeError(err.Error())
	}

	return cmdDesc.Proc(ctx, b)
}


func syntaxValid(ctx *CmdContext, cons Constraint) error {
	arity := cons.Arity
	argc := len(ctx.Args) + 1

	// 检查命令长度是否符合要求
	if (arity > 0 && argc != arity) || (arity < 0 && argc < -arity) {
		return ErrWrongArgs(ctx.Name)
	}

	return nil
}